#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>

#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

class RSATool
{
public:
    RSATool()
    {
        // m_padding = RSA_NO_PADDING;
        m_padding = RSA_PKCS1_PADDING;
        base64_chars =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            "abcdefghijklmnopqrstuvwxyz"
            "0123456789+/";
    }
    int public_encrypt(unsigned char *data, int data_len, unsigned char *key,
                       unsigned char *encrypted)
    {
        RSA *rsa = createRSA(key, 1);
        int result = RSA_public_encrypt(data_len, data, encrypted, rsa, m_padding);
        if (rsa != NULL)
            RSA_free(rsa);
        return result;
    }
    int private_decrypt(unsigned char *enc_data, int data_len, unsigned char *key,
                        unsigned char *decrypted)
    {
        RSA *rsa = createRSA(key, 0);
        int result = RSA_private_decrypt(data_len, enc_data, decrypted, rsa, m_padding);
        if (rsa != NULL)
            RSA_free(rsa);
        return result;
    }
    int private_encrypt(unsigned char *data, int data_len, unsigned char *key,
                        unsigned char *encrypted)
    {
        RSA *rsa = createRSA(key, 0);
        int result = RSA_private_encrypt(data_len, data, encrypted, rsa, m_padding);
        if (rsa != NULL)
            RSA_free(rsa);
        return result;
    }
    int public_decrypt(unsigned char *enc_data, int data_len, unsigned char *key,
                       unsigned char *decrypted)
    {
        RSA *rsa = createRSA(key, 1);
        int result = RSA_public_decrypt(data_len, enc_data, decrypted, rsa, m_padding);
        if (rsa != NULL)
            RSA_free(rsa);
        return result;
    }
    void printLastError(const char *msg)
    {
        char *err = (char *)malloc(130);
        ERR_load_crypto_strings();
        ERR_error_string(ERR_get_error(), err);
        printf("%s ERROR: %s\n", msg, err);
        free(err);
    }


    bool public_encryptString(const std::string &InStr, const std::string &InPublicKey, std::string &OutString)
    {
        // Load key
        // FILE* f = fopen(InPublicKey.c_str(), "r");
        // EVP_PKEY* pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
        // fclose(f);

        // 将字符串转换为BIO对象
        BIO *bio = BIO_new_mem_buf(InPublicKey.c_str(), -1);
        // 从BIO对象中读取公钥
        EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
        // 释放BIO对象
        BIO_free(bio);

        // Create/initialize context
        EVP_PKEY_CTX *ctx;
        ctx = EVP_PKEY_CTX_new(pkey, NULL);
        EVP_PKEY_encrypt_init(ctx);

        // Specify padding: default is PKCS#1 v1.5
        // EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING);
        // EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
        // EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); // for OAEP with SHA1 for both digests

        // Encryption
        size_t ciphertextLen;
        EVP_PKEY_encrypt(ctx, NULL, &ciphertextLen, (const unsigned char *)InStr.c_str(), InStr.size());
        unsigned char *ciphertext = (unsigned char *)OPENSSL_malloc(ciphertextLen);
        EVP_PKEY_encrypt(ctx, ciphertext, &ciphertextLen, (const unsigned char *)InStr.c_str(), InStr.size());
        OutString.assign((char *)ciphertext, ciphertextLen);

        // Release memory
        EVP_PKEY_free(pkey);
        EVP_PKEY_CTX_free(ctx);
        OPENSSL_free(ciphertext);

        return true; // add exception/error handling
    }

public:
    std::string base64_encode(unsigned char const *buf, unsigned int bufLen)
    {
        std::string ret;
        int i = 0;
        int j = 0;
        unsigned char char_array_3[3];
        unsigned char char_array_4[4];

        while (bufLen--)
        {
            char_array_3[i++] = *(buf++);
            if (i == 3)
            {
                char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
                char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
                char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
                char_array_4[3] = char_array_3[2] & 0x3f;

                for (i = 0; (i < 4); i++)
                    ret += base64_chars[char_array_4[i]];
                i = 0;
            }
        }

        if (i)
        {
            for (j = i; j < 3; j++)
                char_array_3[j] = '\0';

            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for (j = 0; (j < i + 1); j++)
                ret += base64_chars[char_array_4[j]];

            while ((i++ < 3))
                ret += '=';
        }

        return ret;
    }

    std::vector<unsigned char> base64_decode(std::string const &encoded_string)
    {
        int in_len = encoded_string.size();
        int i = 0;
        int j = 0;
        int in_ = 0;
        unsigned char char_array_4[4], char_array_3[3];
        std::vector<unsigned char> ret;

        while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
        {
            char_array_4[i++] = encoded_string[in_];
            in_++;
            if (i == 4)
            {
                for (i = 0; i < 4; i++)
                    char_array_4[i] = base64_chars.find(char_array_4[i]);

                char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
                char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
                char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

                for (i = 0; (i < 3); i++)
                    ret.push_back(char_array_3[i]);
                i = 0;
            }
        }

        if (i)
        {
            for (j = i; j < 4; j++)
                char_array_4[j] = 0;

            for (j = 0; j < 4; j++)
                char_array_4[j] = base64_chars.find(char_array_4[j]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (j = 0; (j < i - 1); j++)
                ret.push_back(char_array_3[j]);
        }

        return ret;
    }

private:
    RSA *createRSA(unsigned char *key, int publicKey)
    {
        RSA *rsa = NULL;
        BIO *keybio;
        keybio = BIO_new_mem_buf(key, -1);
        if (keybio == NULL)
        {
            printf("Failed to create key BIO");
            return 0;
        }
        if (publicKey)
        {
            rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
        }
        else
        {
            rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
        }
        return rsa;
    }
    RSA *createRSAWithFilename(char *filename, int publicKey)
    {
        FILE *fp = fopen(filename, "rb");
        if (fp == NULL)
        {
            printf("Unable to open file %s \n", filename);
            return NULL;
        }
        RSA *rsa = RSA_new();
        if (publicKey)
        {
            rsa = PEM_read_RSA_PUBKEY(fp, &rsa, NULL, NULL);
        }
        else
        {
            rsa = PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL);
        }
        return rsa;
    }
    inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); }

private:
    int m_padding;
    std::string base64_chars;
};

int main()
{
    std::string pubKey =
        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdvLtwGkWfX2N1E+6ASk3KBYXz3ZX60sZxAhEr72+"
        "UhcY9pDC8Of50jZgYKo3nW5bnVurgTRt+"
        "sBIYy8sTkcaAjL0jhkC32sklBrYj82g0FubIIP9I1AyEziFbyaRHafgLIFZX5NAyp44DHW+"
        "8sXvfPuILWhUalK0x1IPSgv73CQIDAQAB";
    std::string tempPubKey;
    unsigned char plainText[2048 / 8] = "Hello this is Ravi";
    unsigned char publicKey[] =
        "-----BEGIN PUBLIC KEY-----\n"
        "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY\n"
        "ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+\n"
        "vw1HocOAZtWK0z3r26uA8kQYOKX9Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQAp\n"
        "fc9jB9nTzphOgM4JiEYvlV8FLhg9yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68\n"
        "i6T4nNq7NWC+UNVjQHxNQMQMzU6lWCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoV\n"
        "PpY72+eVthKzpMeyHkBn7ciumk5qgLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUy\n"
        "wQIDAQAB\n"
        "-----END PUBLIC KEY-----\n";

    unsigned char privateKey[] =
        "-----BEGIN RSA PRIVATE KEY-----\n"
        "MIIEowIBAAKCAQEAy8Dbv8prpJ/0kKhlGeJYozo2t60EG8L0561g13R29LvMR5hy\n"
        "vGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+vw1HocOAZtWK0z3r26uA8kQYOKX9\n"
        "Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQApfc9jB9nTzphOgM4JiEYvlV8FLhg9\n"
        "yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68i6T4nNq7NWC+UNVjQHxNQMQMzU6l\n"
        "WCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoVPpY72+eVthKzpMeyHkBn7ciumk5q\n"
        "gLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUywQIDAQABAoIBADhg1u1Mv1hAAlX8\n"
        "omz1Gn2f4AAW2aos2cM5UDCNw1SYmj+9SRIkaxjRsE/C4o9sw1oxrg1/z6kajV0e\n"
        "N/t008FdlVKHXAIYWF93JMoVvIpMmT8jft6AN/y3NMpivgt2inmmEJZYNioFJKZG\n"
        "X+/vKYvsVISZm2fw8NfnKvAQK55yu+GRWBZGOeS9K+LbYvOwcrjKhHz66m4bedKd\n"
        "gVAix6NE5iwmjNXktSQlJMCjbtdNXg/xo1/G4kG2p/MO1HLcKfe1N5FgBiXj3Qjl\n"
        "vgvjJZkh1as2KTgaPOBqZaP03738VnYg23ISyvfT/teArVGtxrmFP7939EvJFKpF\n"
        "1wTxuDkCgYEA7t0DR37zt+dEJy+5vm7zSmN97VenwQJFWMiulkHGa0yU3lLasxxu\n"
        "m0oUtndIjenIvSx6t3Y+agK2F3EPbb0AZ5wZ1p1IXs4vktgeQwSSBdqcM8LZFDvZ\n"
        "uPboQnJoRdIkd62XnP5ekIEIBAfOp8v2wFpSfE7nNH2u4CpAXNSF9HsCgYEA2l8D\n"
        "JrDE5m9Kkn+J4l+AdGfeBL1igPF3DnuPoV67BpgiaAgI4h25UJzXiDKKoa706S0D\n"
        "4XB74zOLX11MaGPMIdhlG+SgeQfNoC5lE4ZWXNyESJH1SVgRGT9nBC2vtL6bxCVV\n"
        "WBkTeC5D6c/QXcai6yw6OYyNNdp0uznKURe1xvMCgYBVYYcEjWqMuAvyferFGV+5\n"
        "nWqr5gM+yJMFM2bEqupD/HHSLoeiMm2O8KIKvwSeRYzNohKTdZ7FwgZYxr8fGMoG\n"
        "PxQ1VK9DxCvZL4tRpVaU5Rmknud9hg9DQG6xIbgIDR+f79sb8QjYWmcFGc1SyWOA\n"
        "SkjlykZ2yt4xnqi3BfiD9QKBgGqLgRYXmXp1QoVIBRaWUi55nzHg1XbkWZqPXvz1\n"
        "I3uMLv1jLjJlHk3euKqTPmC05HoApKwSHeA0/gOBmg404xyAYJTDcCidTg6hlF96\n"
        "ZBja3xApZuxqM62F6dV4FQqzFX0WWhWp5n301N33r0qR6FumMKJzmVJ1TA8tmzEF\n"
        "yINRAoGBAJqioYs8rK6eXzA8ywYLjqTLu/yQSLBn/4ta36K8DyCoLNlNxSuox+A5\n"
        "w6z2vEfRVQDq4Hm4vBzjdi3QfYLNkTiTqLcvgWZ+eX44ogXtdTDO7c+GeMKWz4XX\n"
        "uJSUVL5+CVjKLjZEJ6Qc2WZLl94xSwL71E41H4YciVnSCQxVc4Jw\n"
        "-----END RSA PRIVATE KEY-----\n";
    printf("public key:\n%s\n", publicKey);
    printf("private key:\n%s\n", privateKey);
    unsigned char encrypted[4098] = {};
    unsigned char decrypted[4098] = {};
    printf("----------Plain Text----->\n");
    printf("Plain Text = %s\n\n", plainText);

    std::unique_ptr<RSATool> rsaTool = std::unique_ptr<RSATool>(new RSATool);
    int encrypted_length = rsaTool->public_encrypt(
        plainText, strlen(reinterpret_cast<const char *>(plainText)), publicKey, encrypted);
    if (encrypted_length == -1)
    {
        rsaTool->printLastError("Public Encrypt failed ");
        exit(0);
    }
    printf("----------Public Encrypt----->\n");
    std::cout << "Public Encrypt Text = " << rsaTool->base64_encode(encrypted, encrypted_length) << std::endl;
    printf("Public Encrypt Length = %d\n", encrypted_length);
    int decrypted_length =
        rsaTool->private_decrypt(encrypted, encrypted_length, privateKey, decrypted);
    if (decrypted_length == -1)
    {
        rsaTool->printLastError("Private Decrypt failed ");
        exit(0);
    }
    printf("----------Private Decrypt----->\n");
    printf("Private Decrypt Text = %s\n", decrypted);
    printf("Private Decrypt Length = %d\n", decrypted_length);
    encrypted_length = rsaTool->private_encrypt(
        plainText, strlen(reinterpret_cast<const char *>(plainText)), privateKey, encrypted);
    if (encrypted_length == -1)
    {
        rsaTool->printLastError("Private Encrypt failed");
        exit(0);
    }
    printf("----------Private Encrypt----->\n");
    std::cout << "Private Encrypt = " << rsaTool->base64_encode(encrypted, encrypted_length) << std::endl;
    printf("Private Encrypt Length = %d\n", encrypted_length);
    decrypted_length = rsaTool->public_decrypt(encrypted, encrypted_length, publicKey, decrypted);
    if (decrypted_length == -1)
    {
        rsaTool->printLastError("Public Decrypt failed");
        exit(0);
    }
    printf("----------Public Decrypt----->\n");
    printf("Public Decrypt Text = %s\n", decrypted);
    printf("Public Decrypt Length = %d\n", decrypted_length);
    return 0;
}
